#ifndef NET_IOBUFFER_H_
#define NET_IOBUFFER_H_
#include <fermat/util/encode.h>
#include <fermat/common/slice.h>
#include <cstddef>
#include <algorithm>
#include <vector>
#include <cassert>
#include <cstring>
  
namespace fermat {
namespace net {
class IOBuffer {
public:
    static const size_t kPrepend = 8;
    static const size_t kInitSize = 4096;
    
    explicit IOBuffer(size_t initSize = kInitSize)
        : _buffer(kPrepend + initSize),
          _readerIndex(kPrepend),
          _writerIndex(kPrepend)
    {
        assert(readableBytes() == 0);
        assert(writableBytes() == initSize);
        assert(prependableBytes() == kPrepend);
    }
    
   
    void swap(IOBuffer& rhs);
    
    size_t readableBytes() const;

    size_t writableBytes() const;
    
    size_t prependableBytes() const;
    
    const char* peek() const;

    const char* findString(const std::string &str, const char* start = NULL) const;

    const char* findString(const Slice &str, const char* start = NULL) const;

    const char* findChar(const char &c, const char* start = NULL);
    
    const char* findCRLF() const;
    
    const char* findCRLF(const char* start) const;
    
    const char* findEOL() const;
    
    const char* findEOL(const char* start) const;
    
      // retrieve returns void, to prevent
      // string str(retrieve(readableBytes()), readableBytes());
      // the evaluation of two functions are unspecified
      void retrieve(size_t len);

      void retrieveUntil(const char* end);

      void retrieveInt64();
    
      void retrieveInt32();
    
      void retrieveInt16();
    
      void retrieveInt8();
    
      void retrieveAll();
    
     std:: string retrieveAllAsString();

      std::string retrieveAsString(size_t len);

      Slice toSlice() const;
    
      void append(const Slice& str);

      void append(const char* /*restrict*/ data, size_t len);
    
      void append(const void* /*restrict*/ data, size_t len);
    
      void ensureWritableBytes(size_t len);
    
      char* beginWrite();

      const char* beginWrite() const;

      void hasWritten(size_t len);
    
      void unwrite(size_t len);

      void insert(char* index, const char* data, size_t len);

      void insert(char* index, const void* data, size_t len);

      void insertInt64(char* index, int64_t n);

      void insertInt32(char* index, int32_t n);

      void insertInt16(char* index, int16_t n);

      void insertInt8(char* index, int8_t n);

      ///
      /// Append int64_t using network endian
      ///
      void appendInt64(int64_t x);
      ///
      /// Append int32_t using network endian
      ///
      
      
      void appendInt32(int32_t x);
    
      void appendInt16(int16_t x);
    
      void appendInt8(int8_t x);
      ///
      /// Read int64_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int32_t)
      int64_t readInt64();
    
      ///
      /// Read int32_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int32_t)
      int32_t readInt32();
    
      int16_t readInt16();
    
      int8_t readInt8();
    
      ///
      /// Peek int64_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int64_t)
      int64_t peekInt64() const;
    
      ///
      /// Peek int32_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int32_t)
      int32_t peekInt32() const;
    
      int16_t peekInt16() const;
    
      int8_t peekInt8() const;
    
      ///
      /// Prepend int64_t using network endian
      ///
      void prependInt64(int64_t x);
    
      ///
      /// Prepend int32_t using network endian
      ///
      void prependInt32(int32_t x);
    
      void prependInt16(int16_t x);
    
      void prependInt8(int8_t x);
    
      void prepend(const void* data, size_t len);
    
      void shrink(size_t reserve);
    
      size_t Capacity() const;
    
      /// Read data directly into buffer.
      ///
      /// It may implement with readv(2)
      /// @return result of read(2), @c errno is saved
      ssize_t readFd(int fd, int* savedErrno);
    
     private:
    
      char* begin();
    
      const char* begin() const;

      void makeSpace(size_t len);
     private:
      std::vector<char> _buffer;
      size_t _readerIndex;
      size_t _writerIndex;
    
      static const char kCRLF[];
    };



///
///inlines
///

inline void IOBuffer::makeSpace(size_t len)
{
      if (writableBytes() + prependableBytes() < len + kPrepend) {
      // FIXME: move readable data
          _buffer.resize(_writerIndex+len);
      } else {
      // move readable data to the front, make space inside buffer
          assert(kPrepend < _readerIndex);
          size_t readable = readableBytes();
          std::copy(begin()+_readerIndex,
                    begin()+_writerIndex,
                    begin()+kPrepend);
          _readerIndex = kPrepend;
          _writerIndex = _readerIndex + readable;
          assert(readable == readableBytes());
      }
}

inline const char* IOBuffer::begin() const
{ 
    return &*_buffer.begin(); 
}

inline char* IOBuffer::begin()
{ 
    return &*_buffer.begin(); 
}

inline void IOBuffer::prependInt32(int32_t x)
{
    int32_t be32 = hton32(x);
    prepend(&be32, sizeof be32);
}
    
inline void IOBuffer::prependInt16(int16_t x)
{
    int16_t be16 = hton16(x);
    prepend(&be16, sizeof be16);
}

inline void IOBuffer::prependInt8(int8_t x)
{
    prepend(&x, sizeof x);
}
    
inline void IOBuffer::prepend(const void* data, size_t len)
{
    assert(len <= prependableBytes());
    _readerIndex -= len;
    const char* d = static_cast<const char*>(data);
    std::copy(d, d+len, begin()+_readerIndex);
}
    
inline void IOBuffer::shrink(size_t reserve)
{
// FIXME: use vector::shrink_to_fit() in C++ 11 if possible.
    IOBuffer other;
    other.ensureWritableBytes(readableBytes()+reserve);
    other.append(toSlice());
    swap(other);
}
    
inline size_t IOBuffer::Capacity() const
{
    return _buffer.capacity();
}

inline void IOBuffer::prependInt64(int64_t x)
{
    int64_t be64 = hton64(x);
    prepend(&be64, sizeof be64);
}

inline int64_t IOBuffer::peekInt64() const
{
  assert(readableBytes() >= sizeof(int64_t));
  int64_t be64 = 0;
  ::memcpy(&be64, peek(), sizeof be64);
  return ntoh64(be64);
}

inline int32_t IOBuffer::peekInt32() const
{
  assert(readableBytes() >= sizeof(int32_t));
  int32_t be32 = 0;
  ::memcpy(&be32, peek(), sizeof be32);
  return ntoh32(be32);
}

inline int16_t IOBuffer::peekInt16() const
{
  assert(readableBytes() >= sizeof(int16_t));
  int16_t be16 = 0;
  ::memcpy(&be16, peek(), sizeof be16);
  return ntoh16(be16);
}

inline int8_t IOBuffer::peekInt8() const
{
  assert(readableBytes() >= sizeof(int8_t));
  int8_t x = *peek();
  return x;
}

inline void IOBuffer::insert(char* index, const char* data, size_t len)
{
       // char *windex = beginWrite();
    assert((beginWrite() - index) > static_cast<int64_t>(len));
    std::copy(data, data+len, index);
}

inline void IOBuffer::insert(char* index, const void* data, size_t len)
{
    insert(index, static_cast<const char*>(data), len);
}

inline void IOBuffer::insertInt64(char* index, int64_t n)
{
    int64_t be = hton64(n);
    insert(index, &be, sizeof be);
}

inline void IOBuffer::insertInt32(char* index, int32_t n)
{
    int64_t be = hton32(n);
    insert(index, &be, sizeof be);
}

inline void IOBuffer::insertInt16(char* index, int16_t n)
{
    int64_t be = hton16(n);
    insert(index, &be, sizeof be);
}

inline void IOBuffer::insertInt8(char* index, int8_t n)
{
    insert(index, &n, sizeof n);
}

      ///
      /// Append int64_t using network endian
      ///
inline void IOBuffer::appendInt64(int64_t x)
{
    int64_t be64 = hton64(x);
    append(&be64, sizeof be64);
}
    
      ///
      /// Append int32_t using network endian
      ///
      
      
inline void IOBuffer::appendInt32(int32_t x)
{
    int32_t be32 = hton32(x);
    append(&be32, sizeof be32);
}
    
inline void IOBuffer::appendInt16(int16_t x)
{
    int16_t be16 = hton16(x);
    append(&be16, sizeof be16);
}
    
inline void IOBuffer::appendInt8(int8_t x)
{
    append(&x, sizeof x);
}
    
      ///
      /// Read int64_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int32_t)
inline int64_t IOBuffer::readInt64()
{
    int64_t result = peekInt64();
    retrieveInt64();
    return result;
}
    
      ///
      /// Read int32_t from network endian
      ///
      /// Require: buf->readableBytes() >= sizeof(int32_t)
inline int32_t IOBuffer::readInt32()
{
    int32_t result = peekInt32();
    retrieveInt32();
    return result;
}
    
inline int16_t IOBuffer::readInt16()
{
    int16_t result = peekInt16();
    retrieveInt16();
    return result;
}
    
inline int8_t IOBuffer::readInt8()
{
    int8_t result = peekInt8();
    retrieveInt8();
    return result;
}

inline void IOBuffer::append(const Slice& str)
{
    append(str.data(), str.size());
}
    
inline void IOBuffer::append(const char* /*restrict*/ data, size_t len)
{
      ensureWritableBytes(len);
      std::copy(data, data+len, beginWrite());
      hasWritten(len);
}
    
inline void IOBuffer::append(const void* /*restrict*/ data, size_t len)
{
    append(static_cast<const char*>(data), len);
}
    
inline void IOBuffer::ensureWritableBytes(size_t len)
{
    if (writableBytes() < len) {
        makeSpace(len);
     }
    assert(writableBytes() >= len);
}
    
inline char* IOBuffer::beginWrite()
{ 
    return begin() + _writerIndex; 
}
    
inline const char* IOBuffer::beginWrite() const
{ 
    return begin() + _writerIndex; 
}
    
inline void IOBuffer::hasWritten(size_t len)
{
    assert(len <= writableBytes());
    _writerIndex += len;
}
    
inline void IOBuffer::unwrite(size_t len)
{
    assert(len <= readableBytes());
    _writerIndex -= len;
}

inline Slice IOBuffer::toSlice() const
{
    return Slice(peek(), static_cast<int>(readableBytes()));
}


inline void IOBuffer::swap(IOBuffer& rhs)
{
    _buffer.swap(rhs._buffer);
    std::swap(_readerIndex, rhs._readerIndex);
    std::swap(_writerIndex, rhs._writerIndex);
}

inline size_t IOBuffer::readableBytes() const
{ 
    return _writerIndex - _readerIndex;
}

inline size_t IOBuffer::writableBytes() const
{ 
    return _buffer.size() - _writerIndex; 
}

inline size_t IOBuffer::prependableBytes() const
{ 
    return _readerIndex; 
}

inline const char* IOBuffer::peek() const
{
    return begin() + _readerIndex;
}

inline const char* IOBuffer::findString(const std::string &str, const char* start) const
{
    if(start) {
        assert(peek() <= start );
        assert(start <= beginWrite() );
    } else {
        start = peek();
    }
    const char* find = std::search(start, beginWrite(), 
                                str.c_str(), 
                                str.c_str() + str.length());
    return find == beginWrite() ? NULL : find;
}     

inline const char* IOBuffer::findString(const Slice &str, const char* start) const
{
      if(start) {
          assert(peek() <= start );
          assert(start <= beginWrite() );
      } else {
          start = peek();
      }
      const char* find = std::search(start, beginWrite(), 
                                  str.data(), 
                                  str.data() + str.size());
      return find == beginWrite() ? NULL : find;
}  

inline const char* IOBuffer::findChar(const char &c, const char* start)
{
    if (start) {
        assert(peek() <= start);
        assert(start <= beginWrite());
    } else {
        start = peek();
    }
    const void* find = memchr(start, c, beginWrite() - start);
    return static_cast<const char*>(find);
    
}
inline const char* IOBuffer::findCRLF() const
{
    const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2);
    return crlf == beginWrite() ? NULL : crlf;
}

inline const char* IOBuffer::findCRLF(const char* start) const
{
    assert(peek() <= start);
    assert(start <= beginWrite());
    const char* crlf = std::search(start, beginWrite(), kCRLF, kCRLF+2);
    return crlf == beginWrite() ? NULL : crlf;
}

inline const char* IOBuffer::findEOL() const
{
    const void* eol = memchr(peek(), '\n', readableBytes());
    return static_cast<const char*>(eol);
}
    
inline const char* IOBuffer::findEOL(const char* start) const
{
      assert(peek() <= start);
      assert(start <= beginWrite());
      const void* eol = memchr(start, '\n', beginWrite() - start);
      return static_cast<const char*>(eol);
}
    
inline void IOBuffer::retrieve(size_t len)
{
      assert(len <= readableBytes());
      if (len < readableBytes()) {
          _readerIndex += len;
      } else {
        retrieveAll();
      }
}
    
inline void IOBuffer::retrieveUntil(const char* end)
{
    assert(peek() <= end);
    assert(end <= beginWrite());
    retrieve(end - peek());
}
    
inline void IOBuffer::retrieveInt64()
{
    retrieve(sizeof(int64_t));    
}
    
inline void IOBuffer::retrieveInt32()
{
    retrieve(sizeof(int32_t));
}
    
inline void IOBuffer::retrieveInt16()
{
    retrieve(sizeof(int16_t));
}
    
inline void IOBuffer::retrieveInt8()
{
    retrieve(sizeof(int8_t));
}
    
inline void IOBuffer::retrieveAll()
{
    _readerIndex = kPrepend;
    _writerIndex = kPrepend;
}
    
inline std:: string IOBuffer::retrieveAllAsString()
{
    return retrieveAsString(readableBytes());;
}
    
inline std::string IOBuffer::retrieveAsString(size_t len)
{
    assert(len <= readableBytes());
    std::string result(peek(), len);
    retrieve(len);
    return result;
}
}
}
#endif
